/**
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package io.gitee.huangjianbo.supos.openapi.httpclient5;

import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONReader;
import com.alibaba.fastjson2.TypeReference;

import io.gitee.huangjianbo.supos.openapi.core.errors.ApiClientException;
import io.gitee.huangjianbo.supos.openapi.core.errors.ErrorResponse;
import io.gitee.huangjianbo.supos.openapi.core.errors.InvalidArgumentsException;
import io.gitee.huangjianbo.supos.openapi.core.errors.InvalidResponseException;
import io.gitee.huangjianbo.supos.openapi.core.http.HttpContentType;
import io.gitee.huangjianbo.supos.openapi.core.http.HttpGetPath;
import io.gitee.huangjianbo.supos.openapi.core.http.HttpHeader;
import io.gitee.huangjianbo.supos.openapi.core.http.HttpHeaders;
import io.gitee.huangjianbo.supos.openapi.core.http.request.RequestArguments;
import io.gitee.huangjianbo.supos.openapi.core.http.response.ListResult;
import io.gitee.huangjianbo.supos.openapi.core.http.response.PageResult;
import io.gitee.huangjianbo.supos.openapi.core.http.response.Result;
import io.gitee.huangjianbo.supos.openapi.core.thread.ClientContext;
import io.gitee.huangjianbo.supos.openapi.core.thread.ClientContextWrapper;
import io.gitee.huangjianbo.supos.openapi.core.tools.StringUtils;

import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.core5.http.HttpEntity;
import org.apache.hc.core5.http.io.entity.EntityUtils;
import org.apache.hc.core5.http.message.BasicClassicHttpRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.*;
import java.nio.charset.StandardCharsets;

/**
 * @author tomcat
 * @version 1.0
 */
public class HttpClient5SuposApiClient extends AbstractHttpClient5SuposApiClient {

    private static final Logger LOGGER = LoggerFactory.getLogger(HttpClient5SuposApiClient.class);

    public HttpClient5SuposApiClient() {
        super();
    }

    @Override
    protected <R> R execute(CloseableHttpClient httpClient, BasicClassicHttpRequest httpRequest, Class<R> clazz) throws IOException {
        return httpClient.execute(httpRequest, response -> {
            final int status = response.getCode();
            final HttpEntity entity = response.getEntity();
            String fromJson = "";
            if (entity != null) {
                fromJson = EntityUtils.toString(entity, StandardCharsets.UTF_8);
            }
            log(response, fromJson);

            if (isSuccess(status)) {
                return JSON.parseObject(fromJson, clazz, JSONReader.Feature.IgnoreSetNullValue);
            } else if (isFailure(status)) {
                rethrowInvalidResponseError(status, response.getReasonPhrase(), fromJson);
            }
            return null;
        });
    }

    @Override
    protected <R> Result<R> executeReturnResult(CloseableHttpClient httpClient, BasicClassicHttpRequest httpRequest, Class<R> clazz) throws IOException {
        return httpClient.execute(httpRequest, response -> {
            final int status = response.getCode();
            final HttpEntity entity = response.getEntity();
            String fromJson = "";
            if (entity != null) {
                fromJson = EntityUtils.toString(entity, StandardCharsets.UTF_8);
            }
            log(response, fromJson);

            if (isSuccess(status)) {
                return JSON.parseObject(fromJson, new TypeReference<Result<R>>(clazz) {
                }, JSONReader.Feature.IgnoreSetNullValue);
            } else if (isFailure(status)) {
                rethrowInvalidResponseError(status, response.getReasonPhrase(), fromJson);
            }
            return null;
        });
    }

    @Override
    protected <R> ListResult<R> executeReturnListResult(CloseableHttpClient httpClient, BasicClassicHttpRequest httpRequest, Class<R> clazz) throws IOException {
        return httpClient.execute(httpRequest, response -> {
            final int status = response.getCode();
            final HttpEntity entity = response.getEntity();
            String fromJson = "";
            if (entity != null) {
                fromJson = EntityUtils.toString(entity, StandardCharsets.UTF_8);
            }
            log(response, fromJson);

            if (isSuccess(status)) {
                return JSON.parseObject(fromJson, new TypeReference<ListResult<R>>(clazz) {
                }, JSONReader.Feature.IgnoreSetNullValue);
            } else if (isFailure(status)) {
                rethrowInvalidResponseError(status, response.getReasonPhrase(), fromJson);
            }
            return null;
        });
    }

    @Override
    protected <R> PageResult<R> executeReturnPageResult(CloseableHttpClient httpClient, BasicClassicHttpRequest httpRequest, Class<R> clazz) throws IOException {
        return httpClient.execute(httpRequest, response -> {
            final int status = response.getCode();
            final HttpEntity entity = response.getEntity();
            String fromJson = "";
            if (entity != null) {
                fromJson = EntityUtils.toString(entity, StandardCharsets.UTF_8);
            }
            log(response, fromJson);

            if (isSuccess(status)) {
                TypeReference<PageResult<R>> type = new TypeReference<PageResult<R>>(clazz) {};
                return JSON.parseObject(fromJson, type, JSONReader.Feature.IgnoreSetNullValue);
            } else if (isFailure(status)) {
                rethrowInvalidResponseError(status, response.getReasonPhrase(), fromJson);
            }
            return null;
        });
    }

    @Override
    protected void execute(CloseableHttpClient httpClient, BasicClassicHttpRequest httpRequest) throws IOException {
        httpClient.execute(httpRequest, response -> {
            final int status = response.getCode();
            final HttpEntity entity = response.getEntity();
            String fromJson = "";
            if (entity != null) {
                fromJson = EntityUtils.toString(entity, StandardCharsets.UTF_8);
            }
            log(response, fromJson);
            if (isFailure(status)) {
                rethrowInvalidResponseError(status, response.getReasonPhrase(), fromJson);
            }
            return null;
        });
    }

    @Override
    public <T extends RequestArguments> InputStream download(HttpGetPath path, T args, HttpHeader header) {
        if (!args.validate()) {
            throw new InvalidArgumentsException("The request arguments is invalid");
        }
        try (ClientContext context = new ClientContextWrapper(globalConfig)) {
            CloseableHttpClient httpClient = this.getHttpClient(context);
            if (header == null) {
                header = HttpHeader.of();
            }
            if (!header.containsKey(HttpHeaders.CONTENT_TYPE)) {
                header.add(HttpHeaders.CONTENT_TYPE, HttpContentType.APPLICATION_OCTET_STREAM.getMimeType());
            }
            BasicClassicHttpRequest httpRequest = buildHttpRequest(context, path, args, header);
            httpClient.execute(httpRequest, response -> {
                final int status = response.getCode();
                final HttpEntity entity = response.getEntity();
                if (isSuccess(status)) {
                    log(response, "");
                    if (entity != null) {
                        return entity.getContent();
                    }
                } else if (isFailure(status)) {
                    String fromJson = "";
                    if (entity != null) {
                        fromJson = EntityUtils.toString(entity, StandardCharsets.UTF_8);
                    }
                    log(response, fromJson);
                    rethrowInvalidResponseError(status, response.getReasonPhrase(), fromJson);
                }
                return null;
            });
        } catch (Exception e) {
            throw new ApiClientException("Fail to call supOS service", e);
        }
        return null;
    }

    private void rethrowInvalidResponseError(int status, String reason, String fromJson) {
        LOGGER.error("The request is failed, status={}, reason={}, response={}", status, reason, fromJson);
        if (StringUtils.isNotEmpty(fromJson)) {
            ErrorResponse errorResponse = JSON.parseObject(fromJson, ErrorResponse.class);
            if (errorResponse != null && errorResponse.validate()) {
                throw new InvalidResponseException(status, errorResponse);
            }
        }
        throw new InvalidResponseException(status, reason);
    }

}
